package com.onpositive.mediawiki.propertyparser;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.onpositive.semantic.model.api.meta.IMeta;
import com.onpositive.semantic.model.api.property.IPropertyProvider;
import com.onpositive.units.AbstractPreprocessor;
import com.onpositive.units.AbstractValueParser;
import com.onpositive.units.ParsedDimension;
import com.onpositive.units.ParsedValue;


public class CompositeValueParser {
	
	private static String SERVICE_PATH = "C:\\wikistorage\\" ;
	private static String SERVICE_FILE_NAME = "parsers.xml" ;
	
	private static HashMap<String,HashMap<String,Integer>> parserStatsMap = new HashMap<String, HashMap<String,Integer>>() ;
	private static HashMap<String,HashMap<String,Integer>> unitStatsMap = new HashMap<String, HashMap<String,Integer>>() ;
	
	
	private static class ValueParserRegistry{
		
		private static final int FLAG_OVERWRITE = 1 ;
		
		private HashMap<String, AbstractValueParser> parserMap ;
		private HashMap<String, String> map ;
		private ArrayList<AbstractPreprocessor> preprocessors ;
		private Document document ;
		
		private AbstractValueParser defaultValueParser ;
		
		private ValueParserRegistry()
		{			
			init() ;			
		}
		
		private AbstractValueParser getParser( String propertyName )
		{
			String parserId = map.get(propertyName) ;
			
			AbstractValueParser parser = parserMap.get( parserId ) ;
			
			if( parser != null )
				return parser ;
			else
				return defaultValueParser ;
		}
		
		public String[] preprocess( String propertyName, String propertyValue, IPropertyProvider propertyProvider, IMeta meta )
		{
			String[] param = { propertyName, propertyValue } ;
			String[] result = null ;
			boolean haveChange = false ;
			
			for( AbstractPreprocessor p : preprocessors )
			{
				 result = p.preprocess( param, propertyProvider, meta ) ;
				 
				 if( result != null ){
					 haveChange = true ;
					param = result ;
				 }
			}
			return haveChange ? param : null ;
		}
		
		private void init()
		{
			parserMap = new HashMap<String, AbstractValueParser>() ;
			preprocessors = new ArrayList<AbstractPreprocessor>() ;
			map = new HashMap<String, String>() ;
			
			AbstractValueParser[] parserArr = { new StringValueParser(),
												new DateParser(),
												new LinkArrayParser(),
												new DimensionParser(),
												new ScalarParser()} ;
			
			for( int i = 0 ; i < parserArr.length ; i++ )
			{				
				parserMap.put( parserArr[i].getId(), parserArr[i] ) ;
				
				AbstractPreprocessor p = parserArr[i].getPreprocessor() ;
				if( p != null )
					preprocessors.add(p) ;
			}
			
			
			defaultValueParser = new AbstractValueParser() {
				
				@Override
				public ParsedValue parse(String s, IMeta meta, String comment) {
					try{
					String line = killWikiRefsLeaveCaptions(s) ;
					return UnitParser.parseStatically(line, meta, comment) ;
					}
					catch(Exception e){
						return null ;						
					}
				}

				@Override
				public AbstractPreprocessor getPreprocessor() {
					return null;
				}
			};
			
			preprocessors.add( new AbstractPreprocessor() {
				
				private String[] needToBeKilledStrings = { "main", "alt", "more", "note" };
				@Override
				public String[] preprocess( String[] param, IPropertyProvider propertyProvider, IMeta meta ) {
					String name = param[0].trim() ;
					for( String s : needToBeKilledStrings )
					{
						if( name.startsWith(s) ){
							name = name.substring( s.length() ).trim() ;
							param[0] = name ;
							return param ;
						}
						if( name.endsWith(s)){
							name = name.substring( 0, name.length()-s.length() ).trim() ;
							param[0] = name ;
							return param ;
						}
					}					
					return null;
				}
			}) ;
			
			fillParserMap(null) ;
			
			produceDocument();
			if( document == null )
				return ;
			
			Element root = document.getDocumentElement() ;
			if( root == null )
				return ;

			NodeList nodeList = root.getChildNodes() ;
			int l = nodeList.getLength() ;
			for( int i = 0 ; i < l ; i++ )
			{
				Node n = nodeList.item(i);
				if( n instanceof Element )
				{
					Element element = (Element)n ;
					String elementName = element.getNodeName() ; 
					if ( elementName != null && elementName.equals("parser-setting") )
					{
						String propertyName = element.getAttribute( PropertyCharacteristic.CHAR_PROPERTY_NAME_LABEL ) ;
						String parserId = element.getAttribute(PropertyCharacteristic.CHAR_PARSER_ID_LABEL) ;
						map.put( propertyName, parserId ) ;					
					}
				}			
			}
			
									
		}
		
		public void fillParserMap( Collection<PropertyCharacteristic> collection )
		{			
			
			if( collection == null )
				return ;
			
			for( PropertyCharacteristic pc : collection )
			{
				String propertyName = pc.getCharacteristic( PropertyCharacteristic.CHAR_PROPERTY_NAME_LABEL ) ;
				String parserId = pc.getCharacteristic( PropertyCharacteristic.CHAR_PARSER_ID_LABEL ) ;
				map.put( propertyName, parserId ) ;				
			}
		}
		
		public void commitParserInfo( Map<String,PropertyCharacteristic> characteristicsMap, int flag )
		{
			if ( parserStatsMap == null || characteristicsMap == null )
				return ;
				
			boolean globalOverwrite = ( flag & FLAG_OVERWRITE ) != 0 ;
			
			for( Map.Entry<String,String> entryGlob : map.entrySet() )
			{
				String propertyName = entryGlob.getKey() ;
				HashMap<String,Integer> propertyStats = parserStatsMap.get( propertyName ) ;
				if( propertyStats == null )
					continue ;
				
				boolean localOverWrite = globalOverwrite ;
				
				PropertyCharacteristic pChar = characteristicsMap.get( propertyName ) ;
				if( pChar == null )
				{
					pChar = new PropertyCharacteristic() ;
					characteristicsMap.put( propertyName, pChar ) ;
					localOverWrite = true ;
				}								
				
				String newParserId = "undef" ;
				Integer newSamplesCount = 0 ;
				double maxCount_Pars = 0 ;
				for( Map.Entry<String, Integer> entryLoc : propertyStats.entrySet() )
				{
					String key = entryLoc.getKey() ;
					int a = entryLoc.getValue() ;
					newSamplesCount += a ;
					if( a > maxCount_Pars && !key.equals("undef") ){
						maxCount_Pars = a ;
						newParserId = key ;
					}							
				}
				Double newRate = maxCount_Pars/newSamplesCount ;
				
				pChar.setCharacteristic( PropertyCharacteristic.CHAR_PROPERTY_NAME_LABEL, propertyName ) ;
				pChar.setCharacteristic( PropertyCharacteristic.CHAR_RATE_LABEL, newRate.toString() ) ;
				pChar.setCharacteristic( PropertyCharacteristic.CHAR_SAMPLES_COUNT_LABEL, newSamplesCount.toString() ) ;
				
				if( localOverWrite ){
					pChar.setCharacteristic( PropertyCharacteristic.CHAR_PARSER_ID_LABEL, newParserId ) ;
					if( newParserId.equals( ParsedDimension.ID ) )
					{
						
						HashMap<String,Integer> propertyUnitStats = unitStatsMap.get( propertyName ) ;
						if( propertyUnitStats == null )
							return ;
						
						String newUnitName = "" ;
						double maxCount_Unit = 0 ;
						for( Map.Entry<String, Integer> entryLoc : propertyUnitStats.entrySet() )
						{
							String key = entryLoc.getKey() ;
							int a = entryLoc.getValue() ;
							newSamplesCount += a ;
							if( a > maxCount_Unit  ){
								maxCount_Unit = a ;
								newUnitName = key ;
							}							
						}
						pChar.setCharacteristic( PropertyCharacteristic.CHAR_UNIT_LABEL, newUnitName ) ;

						
					}
				}
			}
		}

		private void produceDocument()
		{
			DocumentBuilder builder ;		
			try {
				builder = DocumentBuilderFactory.newInstance().newDocumentBuilder() ;
			} catch (ParserConfigurationException e) {
				e.printStackTrace();
				return ;
			}
			
			File file = new File( SERVICE_PATH ) ;
			if( !file.exists() )
				file.mkdir() ;

			file = new File( SERVICE_PATH + SERVICE_FILE_NAME ) ;
			
			if( !file.exists() )
				try {
					file.createNewFile() ;
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
				
			try {
				document = builder.parse( new FileInputStream( file ) ) ;
					
			} catch (FileNotFoundException e) {
				try {
					document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
				} catch (ParserConfigurationException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			} catch (SAXException e) {
				try {
					document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
				} catch (ParserConfigurationException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			} catch (IOException e) {
				try {
					document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
				} catch (ParserConfigurationException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}			
		}	
		private void flushStats( HashMap< String, HashMap<String,Integer >> statsMap )
		{
			HashSet<String> processedProperties = new HashSet<String>() ;
			Element root = document.getDocumentElement() ;
			if( root == null )
				root = document.createElement("parser-rules") ;
			
			NodeList nodeList = root.getChildNodes() ;
			int l = nodeList.getLength() ;
			for( int i = 0 ; i < l ; i++ )
			{
				Node n = nodeList.item(i);
				if( n instanceof Element )
				{
					Element element = (Element)n ;
					if ( element.getNodeName().equals("parser-setting") )
					{
						boolean fixed = Boolean.parseBoolean( element.getAttribute( "fixed" ) );
						if( fixed )
							continue ;
						
						String propertyName = element.getAttribute( PropertyCharacteristic.CHAR_PROPERTY_NAME_LABEL ) ;
						double rate = Double.parseDouble(element.getAttribute(PropertyCharacteristic.CHAR_RATE_LABEL) ) ;
						int samplesCount = Integer.parseInt(element.getAttribute(PropertyCharacteristic.CHAR_SAMPLES_COUNT_LABEL) ) ;
						
						processedProperties.add( propertyName ) ;
						
						HashMap<String,Integer> propertyStats = statsMap.get( propertyName ) ;
						if( propertyStats == null )
							continue ;
						
						String newParserId = "undef" ;
						Integer newSamplesCount = 0 ;
						double maxCount = 0 ;
						for( String pId : propertyStats.keySet() )
						{
							int a = propertyStats.get( pId ) ;
							newSamplesCount += a ;
							if( a > maxCount && !pId.equals("undef") ){
								maxCount = a ;
								newParserId = pId ;
							}							
						}
						
						if( newSamplesCount > 0.7 * samplesCount )
						{
							Double newRate = maxCount/newSamplesCount ;
							
							if ( newRate > rate ){
								
								element.setAttribute( PropertyCharacteristic.CHAR_PARSER_ID_LABEL, newParserId ) ;
								element.setAttribute( PropertyCharacteristic.CHAR_RATE_LABEL, newRate.toString() ) ;
								element.setAttribute( PropertyCharacteristic.CHAR_SAMPLES_COUNT_LABEL, newSamplesCount.toString() ) ;								
							}
						}
						
					}
				}			
			}
			for( String propertyName : statsMap.keySet() )
			{
				if( processedProperties.contains(propertyName) )
					continue ;
				
				HashMap<String,Integer> propertyStats = statsMap.get( propertyName ) ;
				if( propertyStats == null )
					continue ;
				
				String newParserId = "undef" ;
				Integer newSamplesCount = 0 ;
				double maxCount = 0 ;
				for( String pId : propertyStats.keySet() )
				{
					int a = propertyStats.get( pId ) ;
					newSamplesCount += a ;
					if( a > maxCount && !pId.equals("undef") ){
						maxCount = a ;
						newParserId = pId ;
					}							
				}
				Double newRate = maxCount/newSamplesCount ;
				
				Element element = document.createElement("parser-setting") ;
				element.setAttribute( PropertyCharacteristic.CHAR_PROPERTY_NAME_LABEL, propertyName ) ;
				element.setAttribute( PropertyCharacteristic.CHAR_PARSER_ID_LABEL, newParserId ) ;
				element.setAttribute( PropertyCharacteristic.CHAR_RATE_LABEL, newRate.toString() ) ;
				element.setAttribute( PropertyCharacteristic.CHAR_SAMPLES_COUNT_LABEL, newSamplesCount.toString() ) ;
				
				root.appendChild( element ) ;				
			}
			
			
			saveStats(root);	
		}

		private static void saveStats(Element root) {
			Transformer transformer ;
			try {
				transformer =  TransformerFactory.newInstance().newTransformer() ;
			} catch (TransformerConfigurationException e) {
				e.printStackTrace();
				return ;
			} catch (TransformerFactoryConfigurationError e) {
				e.printStackTrace();
				return ;
			}
			
			transformer.setOutputProperty( OutputKeys.INDENT, "yes" ) ;
			
			File file = new File( SERVICE_PATH ) ;
			if( !file.exists() )
				file.mkdir() ;

			file = new File( SERVICE_PATH + SERVICE_FILE_NAME ) ;
			
			if( !file.exists() )
				try {
					file.createNewFile() ;
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			
			StreamResult sr = new StreamResult( file ) ;
			try {
				transformer.transform( new DOMSource(root),  sr ) ;
			} catch (TransformerException e) {
				e.printStackTrace();
				return ;
			}
		}	
	}
	
	private static ValueParserRegistry registry = null ;
	
	public static ParsedValue parse( String propertyName, String propertyValue, String comment )
	{
		initRegistry();
		
		String refinedProperyName = refineProperyName( propertyName ) ;
		AbstractValueParser valueParser = registry.getParser( refinedProperyName ) ;
		ParsedValue result = valueParser.parse( propertyValue, null, comment ) ;
		
		if( result != null )
			commitStats( propertyName, result );	
		
		return result ;
	}
	
	public static String[] preprocess( String propertyName, String propertyValue, IPropertyProvider propertyProvider, IMeta meta )
	{
		initRegistry();
		
		return registry.preprocess( propertyName, propertyValue, propertyProvider, meta ) ;		
	}
	public static AbstractValueParser getDefaultParser(){
		
		initRegistry();		
		return registry.defaultValueParser ;		
	}

	

	private static void commitStats(String propertyName, ParsedValue parsedValue )
	{
		String parserId = parsedValue.getId() ;
		HashMap< String,Integer> propertyStatsMap = parserStatsMap.get( propertyName ) ;
		if( propertyStatsMap == null ){
			propertyStatsMap = new HashMap<String, Integer>() ;
			parserStatsMap.put( propertyName, propertyStatsMap ) ;
		}
		Integer count = propertyStatsMap.get( parserId ) ;
		count = ( count != null ) ? count+1 : 1 ;
		propertyStatsMap.put( parserId, count ) ;
		
		if( parsedValue instanceof ParsedDimension )
		{			
			String primaryUnitName = ((ParsedDimension)parsedValue).getUnit().getPrimaryUnit().getShortName() ;
			propertyStatsMap = unitStatsMap.get( propertyName ) ;
			if( propertyStatsMap == null ){
				propertyStatsMap = new HashMap<String, Integer>() ;
				parserStatsMap.put( propertyName, propertyStatsMap ) ;
			}
			count = propertyStatsMap.get( primaryUnitName ) ;
			count = ( count != null ) ? count+1 : 1 ;
			propertyStatsMap.put( primaryUnitName, count ) ;
		}
	}
	
	private static String refineProperyName( String propertyName ){
		
		return propertyName ;
	}
	
	public static void flushStats()
	{
		registry.flushStats( parserStatsMap ) ;		
	}
	public static String killWikiRefsLeaveCaptions( String s )
	{
		String line = s ;
		//get rid of [[knot (unit)|kn]] and [[mph]] -like stuff
		line = line.replaceAll("(\\[\\[)(([^\\[\\]]*)(\\|)|)([^\\[\\]]*)(\\]\\])", "$5");
		line = line.replaceAll("(\\{\\{)([^\\{\\}]*)(\\|)([^\\{\\}]*)(\\}\\})", "$4" ) ;
		return line.trim() ;
	}
	public static String killWikiRefsLeaveRefs( String s )
	{
		String line = s ;
		//get rid of [[knot (unit)|kn]] and [[mph]] -like stuff
		line = line.replaceAll("(\\[\\[)(([^\\[\\]]*)(\\|)|)([^\\[\\]]*)(\\]\\])", "$2");
		line = line.replaceAll("(\\{\\{)([^\\{\\}]*)(\\|)([^\\{\\}]*)(\\}\\})", "$4" ) ;
		return line.trim() ;
	}
	
	public static void grabParserInfo( Collection<PropertyCharacteristic> collection )
	{
		initRegistry() ;
		registry.fillParserMap(collection ) ;
	}
	
	private static void initRegistry() {
		if( registry == null )
			registry = new ValueParserRegistry() ;
	}
	public static void commitParserInfo( Map<String,PropertyCharacteristic> characteristicsMap, int flag )
	{
		if( registry == null )
			registry = new ValueParserRegistry() ;
		
		registry.commitParserInfo(characteristicsMap, flag) ;
	}
	

}
